大型项目程序配置管理演化之路|TW洞见
今日洞见
文章作者、图片来自ThoughtWorks:窦衍森。封面图片来自网络。
本文所有内容,包括文字、图片和音视频资料,版权均属ThoughtWorks公司所有,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表。已经本网协议授权的媒体、网站,在使用时必须注明"内容来源:ThoughtWorks洞见",并指定原文链接,违者本网将依法追究责任。
>>>>前言
CI/CD的精髓在于持续,持续意味着自动化。我们希望自己的程序可以无缝的自动部署在各种环境各个机器上,然而环境的不一致、配置的不同需求,需要我们引入不同的策略来实现不同环境的持续交付。
不管是传统企业,还是互联网公司,现在已经或多或少的实现了自动化部署,但是也或多或少的存在着需要手动干预的步骤,减少手动干预可以有效缩短部署时间,减少出错。自动化部署中不一致的脚本和策略,也会增加出错的概率,或者根本就是错误的设计。
今天我们就来聊聊程序的配置管理,看我们如何在软件发展的各个阶段实现更好的程序配置管理,来支撑持续交付。
>>>>引入
配置管理(注意:没有"程序"二字)定义了程序运行所依赖的一切,及它们之间的唯一关系。包括程序配置、依赖管理(第三方类库等)、环境配置(包括操作系统、网络等等,像Docker、SDN),涵盖除程序源代码生成物之外的一切程序运行的依赖(甚至可以包括主机型号、所需要的电压电流),然后我们可以这样定义它们之间的关系:一个特定的程序,在特定的版本和环境下的配置是什么样的。
详细可以参考《持续交付》这本书,它的范畴很大,所以这里我们只讨论其中的一部分,就是程序配置管理,像连接数据库、上下游依赖,等等。程序配置管理在不同的时期、不同的项目复杂度中可以使用不同的实践。
接下来,就是演进式的介绍不同的配置管理方法。有的方法优雅,但是可能需要更多的维护成本。有的简单粗暴,但是很适合小项目。希望这些可以对你,在做方案选择时,有一些帮助。
>>>>正文
第一阶段:基于文件的程序配置管理
在项目发展的初期,我们通常会把程序配置放到各种配置文件里,app.exe.config, *.ini,xml文件等等。这种方式特别简单,程序可以直接读取配置文件,各种语言都有类库支持这种配置文件的读写,这也是现阶段大部分程序的实现方式。
一起来看一下在基于文件的配置管理下有哪些方法: 项目初始化后,就有了本地的配置文件。很快,我们有了第一次上线。生产环境的配置会和本地开发一样吗?显然不会。怎么办?于是你可能会想到以下几种方案:
I: 不同环境不同的配置文件,不同的生成包
需要准备不同环境的配置文件,使用自动化脚本打包的时候,可以把不同的文件打到不同的包里,如图:
这样,通过维护不同环境的配置文件来实现不同环境的不同配置,打包和部署脚本也非常简单,不需要任何的复杂逻辑。但是,这个方案看看就罢了。每个环境有自己的包,这个是强烈不推荐的,因为每个环境都有自己独立的二进制包。
这意味着:你在测试环境所测试的包,可能会跟生产环境的二进制包不同,不管是什么原因导致的。我们更加期望,在整个过程中只要一份二进制文件,而不同环境的差异仅仅是配置文件。
II: 不同环境不同的配置文件,相同的包
还是有多个配置文件。但是,我们是在部署的时候,用不同的环境配置文件替换默认的文件。如图所示:
我们项目里有很好的实践就是HealthCheck,所有的程序都有一个不需要登录就可以访问的HealthCheck接口,用于显示程序所依赖的上下游服务、数据库是否可用。每次部署完毕通过HealthCheck做Smoke测试来保证部署的正确性。
这个方案已经可以满足一些小的项目需要了,满足了不同环境下的自动化部署需求,部署脚本非常简单,所以项目开始时可以采用这种方式。 随着配置复杂性的慢慢提高,这个方案也逐渐暴露出一些缺点:配置文件中有很多重复项,不同环境的配置文件很多是重复的、公用的,并且要部署一个新的环境就需要增加一套配置文件。
当然,具体有一些技术可以部分解决重复配置的问题,比如配置文件继承,或者分块。有些则很难解决,例如有时候我们需要部署两套环境,但是区别只在于服务的端口号,也就是重复的粒度更小了,用配置文件本身的机制已经不足以满足需求了。所以,我们需要提供一个更灵活的修改管理配置文件的机制。这里,我们引入了另一个方案。
III. 使用同一个配置文件,为不同的环境提供不同的变量
在程序和配置文件的基础上,增加了一个变量层,安装脚本负责把相应的变量写到配置文件中。
例如你可以在脚本中定义{“Dev”:{“DB”:”localhost”}, “Prod”:{“DB”:”10.18.0.10”}}, 或者为不同的环境定义单独的变量文件(eg,dev.ps1, prod.ps1), 在不同的环境部署中会加载不同的变量文件,安装脚本把变量的值写到配置文件中。这样使得重复达到了最小限度。如图:
与第二个方案相比,这个方案的部署脚本变得复杂了,但是整个结构也更加合理。它很好的解决了配置文件重复以及新增环境复杂的问题。
基本这个方案可以解决我们的大部分问题,这也是目前我们正在开发的一个已经进行了七年之久的主要部署方案。因为引入了变量文件,所以部署更灵活。变量可以重用,继承,组合。
到现在为止,我们的软件配置管理还是基于文件的。当需要维护的机器数量到一定量级,分散在各个机器,各个程序上的配置文件的维护成本就变高了。当然,自动化部署,脚本化等技术,可以减缓这种不足。
但是问题依然存在,当因为配置不当引起产品问题后,排查就变得非常困难,需要在不同的机器,不同的地方查看这些文件的配置是不是正确。就跟所有软件设计的驱动力和方法一样。
当软件体量越来越大,我们需要通过分层或者模块化来简化软件的复杂性,使其更容易理解和维护。基于服务的配置管理就是在这样一个背景下产生的。
第二阶段:基于服务的配置管理
配置管理作为单独的服务存在,负责提供一切程序需要的配置信息。有了服务配置管理,程序只需要说,"我需要DB服务",配置管理服务就返回给程序DB IP;“我需要某个service”,配置管理服务就返回这个service的地址。
并且,配置管理服务可以做的更多,比如负载均衡、更容易的服务器增减等等。在不同环境的程序中,只需要知道配置管理服务的地址,其他的服务地址都由配置管理来集中管理。
是不是很简单?是,也不是。程序在架构上更清晰。但是,程序还需要适配新的配置管理方式,以前它就是简单的从config中读取。现在需要跟配置服务交互,可能需要自己实现一些接口。 结构如图:
这样分散在各个系统中的配置信息就可以集中管理了,同时可以增加配置检查功能,对配置的值做初步的校验,防止简单的拼写错误。配置备份也会容易,方便快速搭建环境和恢复业务。
这种方式也是现在一些新项目中使用的方式。当然,两种方式有不同的优缺点。最后,我们来比较下:
文件配置 | 服务配置 | |
---|---|---|
适用场景 | 服务之间依赖简单,机器数量少。 | 依赖复杂,机器数量多 |
复杂性 | 简单,不需要额外的软件支持 | 需要维护相应的配置服务管理,需要程序适配新的配置读取 |
可维护性 | 较差,配置文件散落在各处 | 配置集中管理 |
通过上面的表格可以看到,两者都有自己的缺点和优点。所以,没有最好的,只有最合适的。在不同的阶段,使用不同的策略。
建议开始阶段先使用文件的方式,后期可以切换到服务上。所以,通常我们在做软件架构时,配置读写都会基于接口,这样方便以后切换。
>>>>最后
配置和程序是紧紧配合在一起的。我们曾遇到过许多次这样的情况,程序在本地运行完美,可是到线上就启动不了。我们不得不花费大量的时间去做排查,这都使我们抓耳挠腮,最后才恍然大悟,一个简单的配置错误,可能仅仅是一个字符而已。
程序员常常有“配置没有程序重要”的想法,改改配置是很多程序员不屑去做的事情,但是,从最终用户的角度来看,配置和代码哪个不工作都是问题,只有工作的软件才是最终交付的价值。
- 小编推荐 -
下周六《Scala Cookbook》译者开讲,现场30本书免费送
回复201501-201606,获取当月精彩洞见合辑
如:想看6月精彩洞见合辑,请回复 201606
若你想去 TW洞见网站阅读所有洞见文章,复制网址在浏览器打开:insights.thoughtworkers.org
点击下方【阅读原文】查看更多TW洞见文章